%{

import java.io.*;
import java.util.*;

%}

// token declaration
%token <sval> NUMBER EOS ID ERROR LEFT RIGHT SUB CR NEXT BOOL LOOP UNTIL WHILEP PREDICATE SELECT CASE CASELSE TRUE FALSE RETURN ARRASSIGN FACT OPEN AS ENTRYP
%token <sval> ASSIGN NE EQ GE LE LT GT MUL DIV MINUS PLUS NEGATE STMTS WEND STRINGEXP COLON CASEBLK CASESTMT FUNCTIONCALL CALL INPUT FILENUMBER INPUTF INPUTA
%token <sval> PRINT READ IF THEN ELSE END FOR TO STEP STRING SEMI COMMA OR AND XOR XNOR NOT BREAK CONTINUE PARAM CALLP POWER STRINGEXPT DO WHILE PRINTF
%type <obj> program exp term factor arth_exp stmts stmt procedure procedures call_stmt string_exp case_blk break_stmt continue_stmt declaration_list
%type <obj> print_stmt assign_stmt input_stmt if_stmt while_stmt do_stmt for_stmt boolean_exp predicate_exp select_stmt param_list print_exp open_stmt

// precedence and associativity rules for operators
%left NOT AND XOR XNOR OR 
%left GE LE EQ NE GT LT
%left PLUS MINUS
%left FACT POWER MUL DIV
%left ASSIGN
%nonassoc INUPTA
%nonassoc CALLP

%%

// input is a program or null
input:   /* empty string */
        | input program
       ;
// program is a sequence of statements and/or procedures
// free statements without procedure are grouped into a block called ENTRYP (entry point)
program:  stmts { new Node(Parser.ENTRYP, $1, scanner.yyline(), scanner.yycolumn()); } 
        | procedures { ; }
        ;

// procedures is a series of procedures
procedures : procedures procedure { $$ = null; }
        | procedure { $$ = null; }
        | procedures CR { $$ = $1; } /* any trailing CR's ignored */
        | CR procedures { $$ = $2; } /* any leading CR's ignored */
        ;

// procedure is subroutine/function delimeted by SUB/FUNCTION and END SUB/FUNCTION
// parameters is a declaration_list enclosed by ()
procedure : SUB ID CR stmts END SUB { $$ = new Node(Parser.SUB, $2, $4, null , scanner.yyline(), scanner.yycolumn()); }
        | SUB ID LEFT declaration_list RIGHT CR stmts END SUB { $$ = new Node(Parser.SUB, $2, $7, $4 , scanner.yyline(), scanner.yycolumn()); }
        | SUB ID stmts END SUB { $$ = new Node(Parser.SUB, $2, $3, null, scanner.yyline(), scanner.yycolumn()); }
        | SUB ID LEFT declaration_list RIGHT stmts END SUB { $$ = new Node(Parser.SUB, $2, $6, $4, scanner.yyline(), scanner.yycolumn()); }

// declaration_list a one or more varaibles comma-separated
declaration_list: ID { $$ = new Node(ID, $1, scanner.yyline(), scanner.yycolumn()); }
        | declaration_list COMMA ID { 
            // linked list of varaibles. pointer at left child [0]
            Node t = (Node)$1;
            while (t.child[0] != null) { t = t.child[0]; }
            t.child[0] = new Node(Parser.ID, $3, scanner.yyline(), scanner.yycolumn());
            $$ = $1; // return the head of tree
        }

stmts: stmts stmt { 
	Node t = (Node)$1;
	// append the current stmt to end of node tree.
	while(t.child[0] != null) {
		t = t.child[0];
	}
	t.child[0] = new Node(Parser.STMTS, null, $2, scanner.yyline(), scanner.yycolumn());
        $$ = $1; // return the head of tree
        }
	| stmt { $$ = new Node(Parser.STMTS, null, $1, scanner.yyline(), scanner.yycolumn()); }
        | CR stmts { $$ = $2; } /* leading and trailing blank */
        | stmts CR { $$ = $1; } /* lines are ignored */
        | /* */ { $$ = new Node(Parser.CR, scanner.yyline(), scanner.yycolumn()); }
        ;

stmt :  
          call_stmt 
        | print_stmt
        | input_stmt 
        | open_stmt
        | if_stmt 
        | while_stmt 
        | do_stmt 
        | for_stmt 
        | select_stmt 
        | break_stmt 
        | continue_stmt
        | assign_stmt 
        | error { System.exit(1); } //$$ = new Node(Parser.ERROR, scanner.yyline(), scanner.yycolumn()); }
        ;

// call procedure by CALL or just NAME. parameters are passed between brackets or without
call_stmt: 
          ID { $$ = new Node(Parser.ID, $1, scanner.yyline(), scanner.yycolumn()); }
        | ID param_list { $$ = new Node(Parser.CALLP, $1, $2, scanner.yyline(), scanner.yycolumn()); }
        | ID LEFT factor RIGHT { $$ = new Node(Parser.CALLP, $1, new Node(Parser.PARAM, $3, null, scanner.yyline(), scanner.yycolumn()), scanner.yyline(), scanner.yycolumn()); }
        | ID exp { $$ = new Node(Parser.CALLP, $1, new Node(Parser.PARAM, $2, null, scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
        | ID LEFT param_list RIGHT { $$ = new Node(Parser.CALLP, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | ID LEFT RIGHT { $$ = new Node(Parser.CALLP, $1, null, scanner.yyline(), scanner.yycolumn()); }

        | CALL ID { $$ = new Node(Parser.ID, $2, scanner.yyline(), scanner.yycolumn()); }
        | CALL ID param_list { $$ = new Node(Parser.CALLP, $2, $3, scanner.yyline(), scanner.yycolumn()); }
        | CALL ID LEFT factor RIGHT { $$ = new Node(Parser.CALLP, $2, new Node(Parser.PARAM, $4, null, scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
        | CALL ID exp { $$ = new Node(Parser.CALLP, $2, new Node(Parser.PARAM, $3, null, scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
        | CALL ID LEFT param_list RIGHT { $$ = new Node(Parser.CALLP, $2, $4, scanner.yyline(), scanner.yycolumn()); }
        | CALL ID LEFT RIGHT { $$ = new Node(Parser.CALLP, $2, null, scanner.yyline(), scanner.yycolumn()); }
        ;

// parameter list is a linked list of values. right child [1] is the pointer
param_list: exp { $$ = new Node(Parser.PARAM, $1, null, scanner.yyline(), scanner.yycolumn()); }
        | param_list COMMA exp { 
            // fig 002
            Node t = (Node)$1;
            while (t.child[1] != null) { t = t.child[1]; }
            t.child[1] = new Node(Parser.PARAM, $3, null, scanner.yyline(), scanner.yycolumn());
            $$ = $1;
        }

// IF THEN ELSE
if_stmt: IF boolean_exp THEN stmts END IF { $$ = new Node(Parser.IF, $2, $4, scanner.yyline(), scanner.yycolumn()); }	
        | IF boolean_exp THEN stmts ELSE stmts END IF { $$ = new Node(Parser.ELSE, $2, $4, $6, scanner.yyline(), scanner.yycolumn()); }
	;

// WHILE WEND
while_stmt: WHILE boolean_exp stmts WEND { $$ = new Node(Parser.WEND, $2, $3, scanner.yyline(), scanner.yycolumn()); }

// DO LOOP WHILE
do_stmt: DO stmts LOOP WHILE boolean_exp { $$ = new Node(Parser.DO, $5, $2, scanner.yyline(), scanner.yycolumn()); }

// FOR TO STEP NEXT
for_stmt: FOR ID ASSIGN exp TO exp CR stmts NEXT ID { $$ = new Node(Parser.FOR, $2, $4, $6, $8, scanner.yyline(), scanner.yycolumn()); }
        | FOR ID ASSIGN exp TO exp STEP exp CR stmts NEXT ID { $$ = new Node(Parser.STEP, $2, $4, $6, $8, $10, scanner.yyline(), scanner.yycolumn()); }
        ;

// EXIT SUB/FUNCTION FOR DO
break_stmt: BREAK { $$ = new Node(Parser.BREAK, scanner.yyline(), scanner.yycolumn()); }
        | BREAK SUB { $$ = new Node(Parser.BREAK, scanner.yyline(), scanner.yycolumn()); }
        | BREAK DO { $$ = new Node(Parser.BREAK, scanner.yyline(), scanner.yycolumn()); }
        | BREAK FOR { $$ = new Node(Parser.BREAK, scanner.yyline(), scanner.yycolumn()); }

// CONTINUE        
continue_stmt: CONTINUE { $$ = new Node(Parser.CONTINUE, scanner.yyline(), scanner.yycolumn()); }

// ASSIGNMENT
assign_stmt : ID ASSIGN exp { $$ = new Node(Parser.ASSIGN, $1, $3, scanner.yyline(), scanner.yycolumn()); }
          | ID LEFT factor RIGHT ASSIGN exp { $$ = new Node(Parser.ARRASSIGN, $1, $3, $6, scanner.yyline(), scanner.yycolumn()); }

// OPEN AS
open_stmt: OPEN exp AS FILENUMBER { $$ = new Node(Parser.OPEN, $2, $4, scanner.yyline(), scanner.yycolumn()); }

// PRINT
print_stmt : PRINT print_exp { $$ = new Node(Parser.PRINT, 
                                                new Node(Parser.STRINGEXP, $2, new Node(Parser.STRING, "\n", scanner.yyline(), scanner.yycolumn()), scanner.yyline(), scanner.yycolumn() ),
                                    scanner.yyline(), scanner.yycolumn()); }
          | PRINT print_exp COMMA { $$ = new Node(Parser.PRINT, new Node(Parser.STRINGEXP, $2, new Node(Parser.STRING, TAB, scanner.yyline(), scanner.yycolumn()), scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT print_exp SEMI { $$ = new Node(Parser.PRINT, $2, scanner.yyline(), scanner.yycolumn()); }
          | PRINT COMMA { $$ = new Node(Parser.PRINT, new Node(Parser.STRING, TAB, scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT SEMI { $$ = new Node(Parser.PRINT, new Node(Parser.STRING, "", scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT /**/ { $$ = new Node(Parser.PRINT, new Node(Parser.STRING, "\n", scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }

          | PRINT FILENUMBER COMMA print_exp
 { $$ = new Node(Parser.PRINTF, $2, new Node(Parser.STRINGEXP, $4, new Node(Parser.STRING, "\r\n", scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn() ),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT FILENUMBER COMMA print_exp COMMA 
{ $$ = new Node(Parser.PRINTF, new Node(Parser.STRINGEXP, $4, new Node(Parser.STRING, TAB, scanner.yyline(), scanner.yycolumn()), scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT FILENUMBER COMMA print_exp SEMI 
{ $$ = new Node(Parser.PRINTF, $4, scanner.yyline(), scanner.yycolumn()); }
          | PRINT FILENUMBER COMMA 
{ $$ = new Node(Parser.PRINTF, new Node(Parser.STRING, TAB, scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT FILENUMBER SEMI 
{ $$ = new Node(Parser.PRINTF, new Node(Parser.STRING, "", scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }
          | PRINT FILENUMBER /**/ 
{ $$ = new Node(Parser.PRINTF, new Node(Parser.STRING, "\r\n", scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }

print_exp: exp { $$ = $1; }
        | print_exp COMMA exp { $$ = new Node(Parser.STRINGEXPT, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | print_exp SEMI exp { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }

input_stmt :  INPUT ID { System.out.println("*1"); $$ = new Node(Parser.INPUT, $2, null, scanner.yyline(), scanner.yycolumn()); }
        | INPUT string_exp COMMA ID { System.out.println("*2"); $$ = new Node(Parser.INPUT, $4, $2, scanner.yyline(), scanner.yycolumn()); } /* note order */
        | INPUT string_exp SEMI ID { System.out.println("*3"); $$ = new Node(Parser.INPUT, $4, $2, scanner.yyline(), scanner.yycolumn()); }
        
        | INPUT ID LEFT exp RIGHT {System.out.println("*4");  $$ = new Node(Parser.INPUTA, $2, null, $4, scanner.yyline(), scanner.yycolumn()); }
        | INPUT string_exp COMMA ID LEFT exp RIGHT {System.out.println("*5");  $$ = new Node(Parser.INPUTA, $4, $2, $6, scanner.yyline(), scanner.yycolumn()); }
        | INPUT string_exp SEMI ID LEFT exp RIGHT { System.out.println("*6"); $$ = new Node(Parser.INPUT, $4, $2, $6, scanner.yyline(), scanner.yycolumn()); }

        | INPUT FILENUMBER COMMA ID { System.out.println("*7"); $$ = new Node(Parser.INPUTF, $4, new Node(Parser.FILENUMBER, $2, scanner.yyline(), scanner.yycolumn()),  scanner.yyline(), scanner.yycolumn()); }

select_stmt: SELECT CASE exp CR case_blk END SELECT { $$ = new Node(Parser.SELECT, $3, $5, scanner.yyline(), scanner.yycolumn()); }

// needs improvments: IS RELATIVE, and x TO y
case_blk: CASE exp stmts { $$ = new Node(Parser.CASEBLK, $2, $3, scanner.yyline(), scanner.yycolumn()); }
        | case_blk CASE exp stmts { 
        Node t = (Node)$1;
	// append the current stmt to end of node tree.
	while(t.val != null) {
		t = (Node)t.val;
	}
	t.val = new Node(Parser.CASEBLK, $3, $4, scanner.yyline(), scanner.yycolumn());
        $$ = $1; // return the head of tree
}
        | case_blk CASE ELSE stmts { 
        Node t = (Node)$1;
	// append the current stmt to end of node tree.
	while(t.val != null) {
		t = (Node)t.val;
	}
	t.val = new Node(Parser.CASELSE, $4, scanner.yyline(), scanner.yycolumn());
        $$ = $1; // return the head of tree
}

/* logical or arithmetic */
exp : arth_exp { $$ = $1; }
        | boolean_exp { $$ = $1; }
        | string_exp { $$ = $1; }

string_exp : STRING { $$ = new Node(Parser.STRING, $1, scanner.yyline(), scanner.yycolumn()); }
        | string_exp PLUS string_exp { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | string_exp PLUS arth_exp { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | string_exp PLUS boolean_exp { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | arth_exp PLUS string_exp { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }        
        | boolean_exp PLUS string_exp { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | boolean_exp PLUS STRING { $$ = new Node(Parser.STRINGEXP, $1, $3, scanner.yyline(), scanner.yycolumn()); }


boolean_exp: 
        // exp is surely a boolean-valued
         exp AND exp { $$ = new Node(Parser.BOOL, $1, $3, Parser.AND, scanner.yyline(), scanner.yycolumn()); }
        | exp OR exp { $$ = new Node(Parser.BOOL, $1, $3, Parser.OR, scanner.yyline(), scanner.yycolumn()); }
        | exp XOR exp { $$ = new Node(Parser.BOOL, $1, $3, Parser.XOR, scanner.yyline(), scanner.yycolumn()); }
        | exp XNOR exp { $$ = new Node(Parser.BOOL, $1, $3, Parser.XNOR, scanner.yyline(), scanner.yycolumn()); }
        | NOT exp { $$ = new Node(Parser.BOOL, $2, null, Parser.NOT, scanner.yyline(), scanner.yycolumn()); }
        | predicate_exp { $$ = $1; }
        | LEFT boolean_exp RIGHT { $$ = $2; }

predicate_exp:
          exp ASSIGN exp { $$ = new Node(Parser.PREDICATE, $1, $3, Parser.EQ, scanner.yyline(), scanner.yycolumn()); }
	| exp NE exp { $$ = new Node(Parser.PREDICATE, $1, $3, Parser.NE, scanner.yyline(), scanner.yycolumn()); }
	| arth_exp LT arth_exp { $$ = new Node(Parser.PREDICATE, $1, $3, Parser.LT, scanner.yyline(), scanner.yycolumn()); }
	| arth_exp GT arth_exp { $$ = new Node(Parser.PREDICATE, $1, $3, Parser.GT, scanner.yyline(), scanner.yycolumn()); }
	| arth_exp LE arth_exp { $$ = new Node(Parser.PREDICATE, $1, $3, Parser.LE, scanner.yyline(), scanner.yycolumn()); }
	| arth_exp GE arth_exp { $$ = new Node(Parser.PREDICATE, $1, $3, Parser.GE, scanner.yyline(), scanner.yycolumn()); }
        | LEFT predicate_exp RIGHT { $$ = $2; }
        | TRUE { $$ = new Node(Parser.TRUE, scanner.yyline(), scanner.yycolumn()); }
        | FALSE {$$ = new Node(Parser.FALSE, scanner.yyline(), scanner.yycolumn()); }

arth_exp : arth_exp PLUS term { $$ = new Node(Parser.PLUS, $1, $3, scanner.yyline(), scanner.yycolumn()); }
	| arth_exp MINUS term { $$ = new Node(Parser.MINUS, $1, $3, scanner.yyline(), scanner.yycolumn()); }
	| term { $$ = $1; }
	;

term: term MUL factor { $$ = new Node(Parser.MUL, $1, $3, scanner.yyline(), scanner.yycolumn()); }
	| term DIV factor { $$ = new Node(Parser.DIV, $1, $3, scanner.yyline(), scanner.yycolumn()); }
	| factor { $$ = $1; }
	;

factor: LEFT exp RIGHT { $$ = $2; }
        | factor FACT { $$ = new Node(Parser.FACT, $1, scanner.yyline(), scanner.yycolumn()); }
        | PLUS factor { $$ = $2; }
        | MINUS factor { $$ = new Node(Parser.NEGATE, $2, scanner.yyline(), scanner.yycolumn()); }
	| NUMBER { $$ = new Node(Parser.NUMBER, $1, scanner.yyline(), scanner.yycolumn()); }
        | factor POWER factor { $$ = new Node(Parser.POWER, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        | FILENUMBER { $$ = new Node(Parser.FILENUMBER, $1, scanner.yyline(), scanner.yycolumn()); }
        | TRUE { $$ = new Node(Parser.TRUE, scanner.yyline(), scanner.yycolumn()); }
        | FALSE {$$ = new Node(Parser.FALSE, scanner.yyline(), scanner.yycolumn()); }
        | ID {  $$ = new Node(Parser.ID, $1, scanner.yyline(), scanner.yycolumn()); }
        | ID LEFT RIGHT { $$ = new Node(Parser.CALLP, $1, null, scanner.yyline(), scanner.yycolumn()); }
        | ID LEFT param_list RIGHT { $$ = new Node(Parser.CALLP, $1, $3, scanner.yyline(), scanner.yycolumn()); }
        ;

%%

Yylex scanner; // a reference to scanner, such that parser peek tokens from
Node program; // program tree

public static final String TAB = "   ";

private int yylex () {
    int yylex_val = -1;
    try {
        yylval = new ParserVal(0);
        // consume tokens from lexer
        yylex_val = scanner.yylex();
    } catch(Exception e) { yyerror("IO error"); }
    return yylex_val;
}

public Parser(Reader r) {
        program = null;
	// scanner now points to new instance of lexer
	scanner = new Yylex(r, this);
}

public Node getProgramTree() {
    return program;
}

public void yyerror(String str) {
        WorkSpace.error(str + " at (" + scanner.yyline() + ", " + scanner.yycolumn() + "): " + scanner.yytext() + "\n" );
}
